#include "DataAcceptor.h"

#include "muduo/base/Logging.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/Socket.h"
//#include "muduo/net/SocketsOps.h"
#include "SocketsOpsExt.h"
#include "muduo/net/InetAddress.h"
#include "muduo/base/Logging.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using namespace ftp;
using namespace muduo;
using namespace muduo::net;

DataAcceptor::DataAcceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
	: loop_(loop),
	acceptSocket_(sockets::createNonblockingSocket(listenAddr.family())),
	acceptChannel_(loop, acceptSocket_.fd()),
	listening_(false),
	reuseport_(reuseport),
	listenAddr_(listenAddr),
	idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))
{
	assert(idleFd_ >= 0);
	acceptSocket_.setReuseAddr(true);
	acceptSocket_.setReusePort(reuseport);

	acceptChannel_.setReadCallback(
		std::bind(&DataAcceptor::handleRead, this));

	LOG_DEBUG << "Create a new DataAcceptor obj@" << this;
}

DataAcceptor::~DataAcceptor()
{
	listening_ = false;
	acceptChannel_.disableAll();
	acceptChannel_.remove();
	::close(idleFd_);

    LOG_DEBUG << "Destroy DataAcceptor obj@" << this;
}

bool DataAcceptor::createSocketAndListen()
{
	createSocketAndListen(listenAddr_);
	return false;
}

bool DataAcceptor::createSocketAndListen(const muduo::net::InetAddress& addr)
{
	loop_->assertInLoopThread();
	if (listening_) return true;

	int ret = acceptSocket_.bindAddressNoDie(addr);
	if (ret < 0) { // error
		listening_ = false;
		LOG_SYSERR << "bind error on " << addr.toIpPort();
	}
	else {
		ret = acceptSocket_.listenNoDie();
		if (ret < 0) { // error
			listening_ = false;
			LOG_SYSERR << "listen error on " << addr.toIpPort();
		}
		else {
			listening_ = true;
			InetAddress localAddr(sockets::getLocalAddr(acceptSocket_.fd()));
			listenAddr_ = localAddr;

			acceptChannel_.enableReading();
			LOG_INFO << "Success to listen on " << addr.toIpPort();
		}
	}

	return listening_;
}

bool DataAcceptor::createSocketAndListen(const muduo::net::InetAddress& baseAddr, uint16_t maxPort)
{
	if (baseAddr.port() > maxPort) return false;
	const std::string ip = baseAddr.toIp();
	for (uint16_t i = baseAddr.port(); i <= maxPort; ++i) {
		InetAddress addr(ip, i);
		if (createSocketAndListen(addr)) {
			return true;
		}
	}
	return false;
}

// @brief Try to create a listen sockfd, listening port in [listenAddr_.ipPort(), maxPort]
// 
// Listening data port is saved to listenAddr_ if listen success.
//
// @return Result of listening on data port. 
// - true: success to create a listen sockfd, which bind, listen on data port.
// - false: fail.
// 
bool DataAcceptor::createSocketAndListen(uint16_t minPort, uint16_t maxPort)
{
	return createSocketAndListen(InetAddress(listenAddr_.toIp(), minPort), maxPort);
}

void DataAcceptor::handleRead()
{
	loop_->assertInLoopThread();

	if (!listening_) return;

	InetAddress peerAddr;
	int connfd = acceptSocket_.accept(&peerAddr);
	if (connfd >= 0) {
		if (newConnectionCallback_) {
			newConnectionCallback_(connfd, peerAddr);
		}
		else {
			sockets::close(connfd);
		}
	}
	else { // error
		LOG_SYSERR << "in DataAccetpr::handleRead";

		if (errno == EMFILE) {
			// EMFILE: Too many open files
			::close(idleFd_);
			idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
			::close(idleFd_);
			idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
		}
	}
}
